#include <nlohmann/json.hpp>
#include "BinanceRestApi.h"

BinanceRestApi::BinanceRestApi(std::string api_key,std::string secret_key):apiKey(api_key),secretKey(secret_key){
    curl_global_init(CURL_GLOBAL_DEFAULT);
}

BinanceRestApi::~BinanceRestApi() {
    curl_global_cleanup();
}

size_t BinanceRestApi::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

std::string BinanceRestApi::getSignature(const std::string& data){
    unsigned char* result;
    static char res_hexstring[65];
    result = HMAC(EVP_sha256(), secretKey.c_str(), secretKey.length(), (unsigned char*)data.c_str(), data.length(), NULL, NULL);
    for (int i = 0; i < 32; i++) {
        sprintf(&(res_hexstring[i * 2]), "%02x", result[i]);
    }
    return std::string(res_hexstring, 64);
}

std::string BinanceRestApi::UnixTimestampMills() {  
    auto now = std::chrono::system_clock::now();  
    auto time_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();  
    return std::to_string(time_since_epoch);  
} 

std::string BinanceRestApi::getSpotQuantity(const std::string& symbol){
    std::string quantity ="0";
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return quantity;
    }
    std::string readBuffer;
    if(curl) {
        std::string query_string = "timestamp=" + UnixTimestampMills();
        std::string url = "https://api.binance.com/api/v3/account?" + query_string + "&signature=" + getSignature(query_string);
        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        CURLcode res = curl_easy_perform(curl);;
        curl_easy_cleanup(curl);
        if(res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        }else{
            //std::cout<<readBuffer<<std::endl;
            nlohmann::json account = nlohmann::json::parse(readBuffer);
            for(const auto& coins : account["balances"]){
                if(coins["asset"] == symbol){
                    quantity = coins["free"];
                    return quantity;
                }
            }
        }  
    }
    return quantity;
}

void BinanceRestApi::send_order(const Order& order){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    if (curl) {
        std::string post_data = "symbol=" + order.symbol + 
                                "&side=" + order.side + 
                                "&type=" + order.type + 
                                "&quantity=" + order.sQuantity + 
                                //"&price=" + order.sPrice +
                                //"&timeInForce=" + order.timeInForce + 
                                "&recvWindow=5000&timestamp=" + UnixTimestampMills();
        std::cout << "[rest send order]: " << order.side << " " << order.symbol << " " << order.sQuantity << " " << UnixTimestampMills() << std::endl;
        std::string payload = post_data + "&signature=" + getSignature(post_data);
        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/order");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        CURLcode res = curl_easy_perform(curl);;
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
        if (res != CURLE_OK){
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }else{
            if (readBuffer.find("\"code\"") != std::string::npos) {
                //std::cout<<"[send order failed]: "<<readBuffer<<std::endl;
                logi("[rest trade failed]: {}",readBuffer);
            }else{
                //std::cout<<"[send order successed]: "<<readBuffer<<std::endl;
                logi("[rest trade successed]: {}",readBuffer);
            }
        }
    }
}


void BinanceRestApi::cancel_order(const std::string &symbol,const std::string& client_order_id){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    std::string post_data = "symbol=" + symbol + 
                            "&origClientOrderId=" + client_order_id +
                            "&timestamp=" + UnixTimestampMills();
    std::string payload = post_data + "&signature=" + getSignature(post_data);
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/order");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    CURLcode res = curl_easy_perform(curl);;
    curl_easy_cleanup(curl);
    if (res != CURLE_OK){
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    }else{
        std::cout<<"cancel_order: "<<readBuffer<<std::endl;
    }
}

void BinanceRestApi::cancel_all_order(const std::string &symbol){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    std::string post_data = "symbol=" + symbol + 
                            "&timestamp=" + UnixTimestampMills();
    std::string payload = post_data + "&signature=" + getSignature(post_data);
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/allOpenOrders");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    CURLcode res = curl_easy_perform(curl);;
    curl_easy_cleanup(curl);
    if (res != CURLE_OK){
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    }else{
        std::cout<<"cancel_all_order: "<<readBuffer<<std::endl;
    }
}

std::vector<std::string> BinanceRestApi::getSymbolList(){
    std::vector<std::string> symbolList;
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return symbolList;
    }
    std::string readBuffer;
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/exchangeInfo");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    CURLcode res = curl_easy_perform(curl);;
    curl_easy_cleanup(curl);
    if(res != CURLE_OK)
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    else {
        try {
            nlohmann::json jsonData = nlohmann::json::parse(readBuffer);
            for (const auto& symbol : jsonData["symbols"]) {
                symbolList.push_back(symbol["symbol"].get<std::string>());
            }
        } catch (const nlohmann::json::parse_error& e) {
            std::cerr << "JSON parsing error: " << e.what() << std::endl;
        }
    }
    return symbolList;
}

void BinanceRestApi::allTradingPairs(std::set<std::string>& allPairs,std::vector<std::string>& allCoins){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/exchangeInfo");
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        CURLcode res = curl_easy_perform(curl);;
        curl_easy_cleanup(curl);
        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            return;
        }
        try {
            // 使用 nlohmann::json 解析 JSON 数据
            std::set<std::string> coinSet;
            auto json = nlohmann::json::parse(readBuffer);
            for (const auto& symbol : json["symbols"]) {
                if (symbol["status"] == "TRADING" && symbol["isSpotTradingAllowed"]) {
                    allPairs.insert(symbol["symbol"].get<std::string>());
                    coinSet.insert(symbol["baseAsset"].get<std::string>());
                    coinSet.insert(symbol["quoteAsset"].get<std::string>());
                }
            }
            for (auto coin : coinSet) {
                allCoins.push_back(coin);
            }
        } catch (const std::exception& e) {
            std::cerr << "Error parsing JSON: " << e.what() << std::endl;
            return;
        }
    }
}

void BinanceRestApi::getSymbolInfo(std::map<std::string, double>& symbol_info){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/exchangeInfo");
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        CURLcode res = curl_easy_perform(curl);;
        curl_easy_cleanup(curl);
        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            return;
        }
        try {
            std::string minQty;
            auto json = nlohmann::json::parse(readBuffer);
            for (const auto& symbol : json["symbols"]) {
                if (symbol["status"] == "TRADING" && symbol["isSpotTradingAllowed"]) {
                    std::string symbolName = symbol["symbol"];
                    for (const auto &filter : symbol["filters"]) {
                        if (filter["filterType"] == "LOT_SIZE") {
                            minQty = filter["minQty"].get<std::string>();
                        }
                    }
                    try {
                        double minQtyDouble = std::stod(minQty);
                        symbol_info[symbolName] = minQtyDouble;
                    } catch (const std::invalid_argument& e) {
                        std::cerr << "Error converting minQty or minNotional to double: " << e.what() << std::endl;
                    } catch (const std::out_of_range& e) {
                        std::cerr << "Out of range error converting minQty or minNotional to double: " << e.what() << std::endl;
                    }
                }
            }
        } catch (const std::exception& e) {
            std::cerr << "Error parsing JSON: " << e.what() << std::endl;
            return;
        }
    }
}

void BinanceRestApi::getAccountBalance(std::map<std::string,double>& coinBalance){
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    std::string readBuffer;
    if (curl) {
        std::string query_string = "timestamp=" + UnixTimestampMills();
        std::string url = "https://api.binance.com/api/v3/account?" + query_string + "&signature=" + getSignature(query_string);
        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        CURLcode res = curl_easy_perform(curl);;
        curl_easy_cleanup(curl);
        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        } else {
            nlohmann::json account = nlohmann::json::parse(readBuffer);
            for (const auto& asset : account["balances"]) {
                std::string assetName = asset["asset"];
                double free = std::stod(asset["free"].get<std::string>());
                coinBalance[assetName] = free;
            }
        }
    }
}

std::string BinanceRestApi::ListenKey(bool isPut){
    std::string listenKey;
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return listenKey;
    }
    std::string readBuffer;
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/userDataStream");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    if(isPut){
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    }
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    CURLcode res = curl_easy_perform(curl);;
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl); 
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        try {
            nlohmann::json jsonData = nlohmann::json::parse(readBuffer);
            if (jsonData.contains("listenKey")) {
                listenKey = jsonData["listenKey"];
            }
        } catch (const nlohmann::json::parse_error& e) {
            std::cerr << "JSON parsing error: " << e.what() << std::endl;
        }
    }
    return listenKey;
}